feat: the agentic surface — capabilities, WebMCP, agent trust, and llms.txt discovery - #211
Open
JoviDeCroock wants to merge 38 commits into
Open
feat: the agentic surface — capabilities, WebMCP, agent trust, and llms.txt discovery#211JoviDeCroock wants to merge 38 commits into
JoviDeCroock wants to merge 38 commits into
Conversation
JoviDeCroock
force-pushed
the
JoviDeCroock/webmcp-agentic-ui-vision
branch
2 times, most recently
from
July 11, 2026 12:12
b80ddc4 to
01089b3
Compare
JoviDeCroock
force-pushed
the
JoviDeCroock/webmcp-agentic-ui-vision
branch
3 times, most recently
from
July 22, 2026 15:54
63e18a7 to
c4ba997
Compare
Add the first stage of the capability graph: typed, protocol-neutral
application operations registered in the app manifest and projected to
direct server invocation, a generated HTTP endpoint, and WebMCP page
tools for in-browser agents.
- New @pracht/capabilities package: defineCapability() with a
dependency-free JSON Schema subset validator, effect classes
(read/write/destructive), named middleware, and explicit exposure.
Unsupported schema keywords, webmcp-without-http, and any exposure of
destructive capabilities fail fast at definition time.
- @pracht/core: capability registry/resolver, invokeCapability() for
loaders/API routes, HTTP dispatch at POST /api/capabilities/<name>
with a typed { ok, data | error } envelope (400 validation with
path-scoped issues, middleware 401/403, redacted 500s, same-origin
CSRF), and a capabilities section in buildAppGraph()/devtools.
- @pracht/vite-plugin: virtual:pracht/capabilities (browser
callCapability over the HTTP projection) and virtual:pracht/webmcp
(feature-detected shim targeting document.modelContext.registerTool,
Chrome 150+ origin trial, navigator.modelContext fallback) — both
generated from static manifest analysis, zero bytes when unused, and
wired into both the full-hydration client entry and the islands
bootstrap. Capability modules stay server-only.
- @pracht/cli: pracht inspect capabilities, inspect_capabilities MCP
tool, and pracht verify contract checks (exposed capabilities need
description/input/output/effect; destructive cannot be exposed).
- examples/basic: notes.search (read, http+webmcp) and notes.create
(write, http) with a /notes route using both invocation paths.
- Tests: validator/pipeline/registry/path-mapping unit tests, codegen
extraction tests, verify-check tests, capability e2e suite (HTTP,
loader, browser, WebMCP smoke via a faked document.modelContext),
and client-bundle isolation assertions.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…, pracht eval Builds the trust layer on top of the capability core (#209): - Web Bot Auth (RFC 9421 HTTP Message Signatures per draft-meunier-web-bot-auth-architecture-02): hand-rolled minimal RFC 8941 parsing, Ed25519 via WebCrypto, static keys plus allowlisted /.well-known/http-message-signatures-directory JWKS fetching with a TTL cache. Opt-in via defineApp({ agents: { webBotAuth } }); identity surfaces as context.agent with observe/require policies (per-capability agentPolicy override → 401 agent_required envelope). Fail closed everywhere. - Destructive capabilities may now expose.http behind a server-verified prepare/commit confirmation flow: 409 confirmation_required with a short-lived HMAC token bound to principal + capability + canonicalized input; commit via x-pracht-confirm with byte-identical input; tampered/ expired/mismatched tokens → 403. Requires PRACHT_CONFIRMATION_SECRET (pracht verify enforces); optional best-effort in-memory single-use cache. webmcp/mcp exposure of destructive capabilities stays disallowed in v1. - Capability audit trail: structured event per dispatch (capability, effect, transport, outcome, status, duration, agent) via setCapabilityAuditHook() or the onCapabilityAudit runtime option. - pracht eval: scripted agent-task harness running evals/**/*.eval.json against the capability HTTP projection, with $steps[n].<path> references, subset output matching, human transcript or --json, exit 1 on failure. - examples/basic: notes.purge (destructive), agent.whoami, agent.ping (require), a working eval scenario; e2e coverage for signed/unsigned agents, policy modes, the confirmation flow, and the eval CLI. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add a `llmsTxt` option to the pracht vite plugin (https://llmstxt.org). When enabled, `pracht build` writes dist/client/llms.txt generated from the resolved app graph and the dev SSR middleware serves /llms.txt live. - @pracht/core: new `buildLlmsTxt()` on @pracht/core/server. Static routes are always listed; dynamic routes only when SSG/ISG with getStaticPaths() (one entry per prerendered instance). Routes with a server-only `markdown` export are annotated as markdown-capable; API routes list their detected methods. Output is deterministic. - @pracht/vite-plugin: `llmsTxt: false | { title?, description?, origin?, include? }` (title/description fall back to package.json); the server module exports generateLlmsTxt only when enabled, so disabled builds are byte-for-byte unchanged. - @pracht/cli: `pracht build` writes dist/client/llms.txt, picked up as a static asset by the Node, Cloudflare (ASSETS binding), and Vercel (Build Output filesystem route) adapters without adapter changes. - create-pracht: generated apps enable `llmsTxt: {}` by default. - Enabled in examples/basic, examples/pages-router, examples/cloudflare; unit tests for the generator, plugin option validation, and codegen; e2e coverage for dev serving and all three build outputs. - Docs: docs/LLMS_TXT.md plus a README bullet. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…docs-site pages, example wiring Combines the capability core + WebMCP projection, the agent trust layer, and llms.txt emission into one branch, and makes the three features tell one story: - buildLlmsTxt() gains a '## Capabilities' section listing HTTP-exposed capabilities with dispatch endpoint, effect class (destructive ones note the confirmation requirement), and description; on by default, excludable via llmsTxt.include, zero output for apps without capabilities. - examples/docs gains /docs/capabilities and /docs/agent-trust pages, an 'Agents' sidebar section, agent-markdown index entries, and an updated LLM content page covering the built-in llmsTxt option. - examples/basic links the /notes capability demo from the public shell nav. - README/VISION_MVP/docs cross-references unified. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- New docs-site page /docs/agents (The Agentic Web) leading the Agents sidebar section: the two-users narrative, one-contract-four-callers, the trust layer, a runnable five-minute tour, and what comes next - Testing recipe grows a 'Testing Capabilities & Agent Surfaces' section: unit testing run() and the attached validateInput/validateOutput, Playwright tests for the HTTP projection and prepare/commit flow, faking document.modelContext for WebMCP, signing Web Bot Auth requests, and pracht eval in CI - examples/basic README documents the five-capability demo, the PRACHT_CONFIRMATION_SECRET requirement, and the eval scenario - Cross-links from Capabilities/Agent Trust pages and the home route's agent-markdown index; prev/next chain now routes through /docs/agents Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… dev banner capabilities Fixes the four DX gaps found while evaluating how easy agent surfaces are to test: - @pracht/core: createCapabilityTestHost() runs the full dispatch pipeline in unit tests without a server. invoke() mirrors invokeCapability(); request() mirrors the HTTP projection including Web Bot Auth policy (simulated agent identities via the agent option — no RFC 9421 signing) and the destructive prepare/commit confirmation flow. invokeCapability's body is extracted into invokeCapabilityOnHost and shared; resolveRegistryModule is now public. Browser stub added. - @pracht/cli: pracht eval --start "<command>" launches the app, polls --url until it answers, runs the scenarios, and stops the whole process group — no second terminal. waitForServer() is unit-tested; an e2e test proves launch, run, and teardown against a fixture server. - @pracht/cli: the pracht dev banner now prints a Capabilities table (name, effect, exposure, dispatch path) when the app registers any, and dev accepts --port (legacy positional and still work — the flag was previously parsed as an unknown boolean and silently ignored). - Fixed pracht inspect capabilities (and the banner + MCP inspect_capabilities) reporting effect=n/a transports=private for every capability: manifest-relative paths now load through the virtual server module's registry instead of raw ssrLoadModule. Docs updated across the site (CLI, Agent Trust, Capabilities, Testing recipe, agents story page), internal docs (AGENT_TRUST.md — auto-start moved out of 'Not built yet' — and CAPABILITIES.md), and examples/basic. Verified: 664 unit tests, 95 e2e, typecheck, oxlint, format, docs-site build all green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…hardening docs pracht typegen now generates capability input/output TypeScript types from the capability graph's JSON Schemas into src/pracht-capabilities.d.ts, registered on Register["capabilities"]. invokeCapability(), the browser's callCapability(), and createCapabilityTestHost().invoke() gain typed overloads that infer both sides from the capability name; the untyped invokeCapability<Output>() form keeps working for unregistered names. The app graph (inspect capabilities --json, MCP inspect_capabilities) now includes each capability's schemas. examples/basic checks in the generated declaration and drops its explicit generics, so the monorepo typecheck continuously exercises the inference path. docs/CAPABILITY_GRAPH.md joins the branch as the design record: status updated to accepted, plus a decision log answering the proposal's nine spike questions and recording the delivery-plan deviations (destructive shipped in v1 behind prepare/commit, WebMCP landed before remote MCP, pracht eval speaks HTTP). Supersedes the copy on PR #207. AGENT_TRUST.md gains an "Operational hardening" section documenting the middleware/app-level story for rate limiting, write idempotency, and result-size limits, and lists them under "Not built yet" so they stay visible. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rms, effect-driven revalidation - @pracht/capabilities now owns the wire contract (capabilityHttpPath, confirmation/transport headers, CapabilityErrorCode union, envelope types, schema->TS printer) plus the shared static extractor (@pracht/capabilities/static) consumed by the framework, the vite plugin, and the CLI — replacing three copies of the path formula and two drifted copies of the extractor. - <Form capability> posts the framework form component to the capability endpoint agents call, with server-side schema coercion of form fields and a no-JS 303-back fallback; typed onCapabilityResult via typegen. - Effect classes drive the client cache: successful non-read browser calls (callCapability / Form) revalidate route data via a settled event. - context.agent is typed end to end (CapabilityContext, PrachtRequestContext); audit events distinguish the webmcp transport; expose.mcp is labeled unserved in verify and the dev banner; eval steps gain "confirm" sugar. - Consolidate the branch's 11 changesets into one net-new-feature changeset; decision log updated with the consolidation pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rebasing onto main crossed the capability work with the API-route typegen and declaration rename that landed there: - `pracht dev`'s watch-mode typegen now passes `capabilitiesOut` (required since capability declarations were added) and treats the generated capability declaration as a generated path, so writing it does not retrigger regeneration. - Export `DEFAULT_CAPABILITIES_OUT` alongside the other typegen defaults. - Update the capability typegen test to the renamed declaration output (`src/pracht.d.ts`, not `src/pracht-routes.d.ts`). - Reformat `FormProps` after merging the capability and Standard Schema form props.
Fixes 19 issues surfaced reviewing the capability graph work, spanning validation bypasses, a confirmation-flow gap, static-extraction correctness, dev/build mismatches, and CLI robustness. See the changeset for the per-package breakdown. Adds regression tests for the security-relevant fixes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Follow-up to the review-fixes commit: the default-export detection
regressed three valid module shapes to a silent null (which drops the
capability from codegen/verify). Accept `export default <id>` without a
trailing semicolon (ASI), `export { <id> as default }`, and
arrow-typed const declarations, and fall back to the sole call site
when a module has exactly one defineCapability() call.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pracht typegen --check requires generated files to byte-match the emitter's output (quoted keys, single-line types) while oxfmt --check requires prettier-style formatting — the two can never both hold, and the formatter rewriting the generated file is what caused the original committed drift. Generated files are not formatter-governed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ysis - Run validateAgentsConfig() outside the VALIDATE_MANIFEST guard: Vite compiles import.meta.env.DEV to false in production server/edge bundles, which stripped the security validation and let a typo'd policy fail open again. - Mask comments before locating the manifest capabilities block so a block-commented example cannot shadow the live registry in verify. - Resolve the default-exported capability to the module-scope binding (brace-depth 0), not a shadowed inner declaration. - Make template-literal scanning interpolation-aware so a nested template in run() no longer truncates extraction and silently privatizes an exposed capability. - Extend the typegen collision guard to basename collisions between the declaration and capabilities outputs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
JoviDeCroock
force-pushed
the
JoviDeCroock/webmcp-agentic-ui-vision
branch
from
July 25, 2026 17:01
3a259b9 to
dd7c96d
Compare
- `pracht verify` resolves root-relative capability registrations
(`() => import("/src/capabilities/x.ts")`) against the project root,
matching the runtime registry and the Vite plugin. They previously
resolved against the manifest directory, never existed on disk, and
were skipped without a check or a warning — silently bypassing the
destructive-exposure and PRACHT_CONFIRMATION_SECRET checks while
verification still reported success. A root-relative reference that is
genuinely missing is now an error, since the manifest check only covers
"./"-relative paths.
- `coerceFormInput()` looks up a field's schema with an own-property
check and writes coerced values with `Object.defineProperty`, so a form
field named `__proto__` or `constructor` can neither pick up an
inherited member as its schema nor vanish into the prototype setter.
Such fields now reach validation as real own properties and are
rejected by `additionalProperties: false` instead of being dropped.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The four patch changesets all fixed capability work introduced by the minor changeset on this same branch, so they never needed their own release-note entries. Fold the still-user-visible behaviors (HTTP path safety, regex/default-export-aware static extraction, submitter formaction and middleware redirects in enhanced forms, islands revalidation, the confirmation gate running inside the middleware chain) into the capability graph changeset and drop the rest. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
buildLlmsTxt()emits a## Capabilitiessection with each HTTP-exposed capability's dispatch endpoint, effect class, and description; destructive capabilities are annotatedrequires confirmation. On by default, excludable viallmsTxt: { include }, byte-identical output for apps without capabilities. Agents discovering a site through llms.txt now find its callable operations, not just its pages.examples/docs): new/docs/capabilitiesand/docs/agent-trustpages, a new "Agents" sidebar section (LLM Content, Capabilities, Agent Trust), entries in the agent-markdown index served at/withAccept: text/markdown, and an updated LLM content page covering the built-inllmsTxtoption alongside the site's curated plugin. Both pages prerender and appear in the site's own llms.txt automatically.examples/basiclinks the/notescapability demo from the public shell nav so the demo is discoverable.Testing
pnpm e2e— 94 passed (includes new assertions: llms.txt Capabilities section in the Node build of examples/basic)pnpm formatpnpm lint— the repolintscript is broken (missing eslint); rannpx oxlint .: 0 warnings, 0 errors (pnpm typecheckalso passes)pnpm test— 646 passed (new: capabilities-section unit tests forbuildLlmsTxt, plugin include-option validation)Checklist
llms-txt-capabilities-section(minor: @pracht/core, @pracht/vite-plugin)🤖 Generated with Claude Code